﻿namespace com.stifel.Extensions.SharePointExtensions
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;

    using com.stifel.Exceptions.SharePointExceptions;
    using com.stifel.Extensions.StreamExtensions;
    using com.stifel.SharePoint;

    using Microsoft.SharePoint;

    public static class SPListItemExtensions
    {
        #region Methods

        /// <summary>
        /// Adds the attachment that is represented by the specified file name to the list item; requires an update to the list item to add the attachment. 
        /// </summary>
        /// <param name="item"></param>
        /// <param name="fileName">Fully qualified name of the file to which to add as an attachment to the list item.</param>
        /// <returns>true if added; otherwise false.</returns>
        public static bool AddAttachment( this SPListItem item, string fileName )
        {
            FileInfo fi = new FileInfo( fileName );
            if ( !fi.Exists )
                throw new FileNotFoundOrAddedAsSPListItemAttachmentException( fileName );
            FileStream stream = fi.OpenRead( );
            byte[ ] data = stream.ToArray( );
            item.Attachments.Add( fileName, data );

            return true;
        }

        /// <summary>
        /// Adds the specified file to the list item; requires an update to the list item to add the attachment.
        /// </summary>
        /// <param name="item"></param>
        /// <param name="file"></param>
        /// <returns></returns>
        public static bool AddAttachment( this SPListItem item, SPFile file )
        {
            byte[ ] data = file.OpenBinary( );
            item.Attachments.Add( file.Name, data );

            return true;
        }

        /// <summary>
        /// Adds the specified files to the list item; requires an update to the list item to add the attachments.
        /// </summary>
        /// <param name="item"></param>
        /// <param name="files"></param>
        /// <returns></returns>
        public static bool AddAttachment( this SPListItem item, List<SPFile> files )
        {
            if ( files != null && files.Count > 0 )
                for ( int i = files.Count - 1; i >= 0; i-- )
                    item.AddAttachment( files[ i ] );

            return true;
        }

        /// <summary>
        /// Adds the attachment that is represented by the specified file name to the list item without requiring an update to the parent list item. 
        /// </summary>
        /// <param name="item"></param>
        /// <param name="fileName">Fully qualified name of the file to which to add as an attachment to the list item.</param>
        /// <returns>A string that contains the URL of the attachment.</returns>
        public static string AddAttachmentNow( this SPListItem item, string fileName )
        {
            Uri uri = new Uri( fileName );
            if ( !uri.IsFile )
                throw new Exception( );
            FileInfo fi = new FileInfo( fileName );
            if ( !fi.Exists )
                throw new FileNotFoundOrAddedAsSPListItemAttachmentException( fileName );
            FileStream stream = fi.OpenRead( );
            byte[ ] data = stream.ToArray( );
            string s = item.Attachments.AddNow( fileName, data );

            return s;
        }

        /// <summary>
        /// Adds the specified file as an attachment to the list item without requiring an update to the parent list item. Returns a string that contains the URL of the attachment.
        /// </summary>
        /// <param name="item"></param>
        /// <param name="file"></param>
        /// <returns>A string that contains the URL of the attachment.</returns>
        public static string AddAttachmentNow( this SPListItem item, SPFile file )
        {
            byte[ ] data = file.OpenBinary( );
            bool u = item.Web.AllowUnsafeUpdates;
            if ( u == false )
                item.Web.AllowUnsafeUpdates = true;
            string s = item.Attachments.AddNow( file.Name, data );
            if ( u == false )
                item.Web.AllowUnsafeUpdates = u;
            return s;
        }

        /// <summary>
        /// Adds the specified files as attachments to the list item without requiring an update to the parent list item.
        /// </summary>
        /// <param name="item"></param>
        /// <param name="files"></param>
        /// <returns></returns>
        public static bool AddAttachmentNow( this SPListItem item, List<SPFile> files )
        {
            if ( files != null && files.Count > 0 )
                for ( int i = files.Count - 1; i >= 0; i-- )
                    item.AddAttachmentNow( files[ i ] );

            return true;
        }

        public static bool ContainsField( this SPListItem item, string internalName )
        {
            return item.Fields.ContainsField( internalName );
        }

        /// <summary>
        /// Copy this list item to the target list. This will also copy any list item attachments to the new item in the target list. Only those columns that have a corresponding definition in the target list are copied. The target column must have the same name and same type as the source column, must not be specified as read-only and must be able to be displayed in edit form. All changes made to the database as a result of this function are updated.
        /// </summary>
        /// <param name="fromItem"></param>
        /// <param name="toList">Specifies the target list.</param>
        /// <returns>true if the copy is successful; otherwise returns false.</returns>
        public static bool Copy( this SPListItem fromItem, SPList toList )
        {
            bool ok = false;
            SPListItem toItem = fromItem.copy( toList, false );
            if ( toItem != null )
            {
                fromItem.CopyAttachments( toItem, false );
                toItem.Update( );
                ok = true;
            }
            return ok;
        }

        /// <summary>
        /// Copy the attachments from this list item to the target list item. Optionally, trigger the Microsoft.SharePoint.SPListItem.Update() method to actually commit the changes to the list item. If updateDatabaseWithChangesMadeToListItem is false, requires an update to the list item commit the changes. 
        /// </summary>
        /// <param name="fromItem"></param>
        /// <param name="toItem">Specifies the target list item to which to copy the attachments.</param>
        /// <param name="updateDatabaseWithChangesMadeToListItem">Specifies whether to trigger the Microsoft.SharePoint.SPListItem.Update() method to actually commit the changes to the list item.</param>
        /// <returns></returns>
        public static bool CopyAttachments( this SPListItem fromItem, SPListItem toItem, bool updateDatabaseWithChangesMadeToListItem )
        {
            bool ok = false;
            if ( toItem != null )
            {
                foreach ( string fileName in fromItem.Attachments )
                {
                    SPFile file = fromItem.ParentList.ParentWeb.GetFile( string.Format( "{0}{1}", fromItem.Attachments.UrlPrefix, fileName ) );
                    byte[ ] data = file.OpenBinary( SPOpenBinaryOptions.SkipVirusScan );
                    toItem.Attachments.Add( fileName, data );
                }
                if ( updateDatabaseWithChangesMadeToListItem )
                    toItem.Update( );
            }
            return ok;
        }

        /// <summary>
        /// Copy this list item to the target list. This will also copy any list item attachments to the new item in the target list. Only those columns that have a corresponding definition in the target list are copied. The target column must have the same name and same type as the source column, must not be specified as read-only and must be able to be displayed in edit form. Requires the Microsoft.SharePoint.SPListItem.Update() method to actually add the item to the target list. 
        /// </summary>
        /// <param name="fromItem"></param>
        /// <param name="toList">Specifies the target list.</param>
        /// <returns>The newly created item within the target list.</returns>
        public static SPListItem CopyItem( this SPListItem fromItem, SPList toList )
        {
            SPListItem toItem = fromItem.copy( toList, false );
            if ( toItem != null )
            {
                fromItem.CopyAttachments( toItem, false );
            }
            return toItem;
        }

        public static T GetValue<T>( this SPListItem item, string internalName )
        {
            return item.getValue<T>( internalName, true );
        }

        public static bool IsValidFieldType( this SPListItem item, string internalName, Type type )
        {
            if ( item.Fields.ContainsField( internalName ) )
            {
                bool ok = false;
                SPFieldType t = item.Fields.GetFieldByInternalName( internalName ).Type;

                if ( type.Equals( typeof( String ) ) )
                {
                    if ( t.IsString( ) )
                        ok = true;
                    else if ( t == SPFieldType.File )
                        ok = true;
                    else if ( t == SPFieldType.Computed )
                        ok = true;
                    else if ( t == SPFieldType.Invalid )
                        ok = true;
                }
                else if ( type.Equals( typeof( List<string> ) ) )
                {
                    if ( t == SPFieldType.MultiChoice )
                        ok = true;
                }
                else if ( type.Equals( typeof( DateTime ) ) ||
                    type.Equals( typeof( DateTime? ) ) )
                {
                    if ( t == SPFieldType.DateTime )
                        ok = true;
                }
                else if ( type.Equals( typeof( Int16 ) ) ||
                    type.Equals( typeof( Int16? ) ) )
                {
                    if ( t.IsInteger( ) )
                        ok = true;
                }
                else if ( type.Equals( typeof( Int32 ) ) ||
                    type.Equals( typeof( Int32? ) ) )
                {
                    if ( t.IsInteger( ) )
                        ok = true;
                }
                else if ( type.Equals( typeof( Int64 ) ) ||
                    type.Equals( typeof( Int64? ) ) )
                {
                    if ( t.IsInteger( ) )
                        ok = true;
                }
                else if ( type.Equals( typeof( Decimal ) ) ||
                    type.Equals( typeof( Decimal? ) ) )
                {
                    if ( t.IsFloatingPointNumber( ) )
                        ok = true;
                }
                else if ( type.Equals( typeof( Double ) ) ||
                    type.Equals( typeof( Double? ) ) )
                {
                    if ( t.IsFloatingPointNumber( ) )
                        ok = true;
                }
                else if ( type.Equals( typeof( Boolean ) ) ||
                    type.Equals( typeof( Boolean? ) ) )
                {
                    if ( t == SPFieldType.Boolean )
                        ok = true;
                }
                else if ( type.Equals( typeof( SPFieldUrlValue ) ) )
                {
                    if ( t == SPFieldType.URL )
                        ok = true;
                }
                else if ( type.Equals( typeof( SPUserInfoExpanded ) ) )
                {
                    if ( t == SPFieldType.User )
                        ok = true;
                }
                else if ( type.Equals( typeof( SPLookupValue ) ) ||
                    type.Equals( typeof( Guid ) ) )
                {
                    if ( t == SPFieldType.Lookup )
                        ok = true;
                }
                else if ( type.Equals( typeof( SPFieldLookupValueCollection ) ) )
                {
                    if ( t == SPFieldType.Lookup )
                        ok = true;
                }
                return ok;
            }
            else
                throw new SharePointListDoesNotContainSpecifiedFieldException( internalName );
        }

        public static void SetValue<T>( this SPListItem item, string internalName, T value )
        {
            item.setValue<T>( internalName, value, true );
        }

        public static T UnsafeGetValue<T>( this SPListItem item, string internalName )
            where T : class
        {
            return item.getValue<T>( internalName, false );
        }

        public static void UnsafeSetValue<T>( this SPListItem item, string internalName, T value )
        {
            item.setValue<T>( internalName, value, false );
        }

        static SPListItem copy( this SPListItem fromItem, SPList toList, bool updateDatabaseWithChangesMadeToListItem )
        {
            SPListItem toItem = null;
            foreach ( SPField fromField in fromItem.Fields )
            {
                if ( toList.Fields.ContainsField( fromField.InternalName ) )
                {
                    SPField toField = toList.Fields.GetFieldByInternalName( fromField.InternalName );
                    if ( !toField.ReadOnlyField )//2010 && toField.CanBeDisplayedInEditForm )
                    {
                        if ( fromField.Type.Equals( toField.Type ) )
                        {
                            if ( toItem == null )
                                toItem = toList.AddItem( );
                            toItem[ toField.Id ] = fromItem[ fromField.Id ];
                        }
                    }
                }
            }
            if ( toItem != null )
            {
                fromItem.CopyAttachments( toItem, false );
                if ( updateDatabaseWithChangesMadeToListItem )
                    toItem.Update( );
            }
            return toItem;
        }

        static List<string> getMultiChoiceValue( this SPListItem item, string internalName, bool safe )
        {
            if ( item.Fields.ContainsField( internalName ) )
            {
                SPFieldType t = item.Fields.GetFieldByInternalName( internalName ).Type;
                if ( t == SPFieldType.MultiChoice )
                {
                    string[ ] s = item[ internalName ] == null ? new string[ 0 ] : ( ( string ) item[ internalName ] ).Split( new string[ ] { ";#" }, StringSplitOptions.RemoveEmptyEntries );
                    if ( s.Length > 0 )
                        return s.ToList( );
                    else
                        return new List<string>( );
                }
                else
                {
                    if ( safe )
                        throw new InvalidFieldTypeException( internalName, SPFieldType.MultiChoice );
                    else
                        return null;
                }
            }
            else
            {
                if ( safe )
                    throw new SharePointListDoesNotContainSpecifiedFieldException( internalName );
                else
                    return null;
            }
        }

        static T getValue<T>( this SPListItem item, string internalName, bool safe )
        {
            if ( item.ContainsField( internalName ) )
            {
                if ( item.IsValidFieldType( internalName, typeof( T ) ) )
                {
                    SPField field = item.Fields.GetFieldByInternalName( internalName );

                    if ( typeof( T ).Equals( typeof( SPFieldUrlValue ) ) )
                        return ( T ) ( item.getValueAsSPFieldUrlValue( internalName, safe ) as object );

                    if ( typeof( T ).Equals( typeof( SPUserInfoExpanded ) ) )
                        return ( T ) ( item.getValueAsSPUserInfoExpanded( internalName, safe ) as object );

                    if ( typeof( T ).Equals( typeof( List<string> ) ) )
                        return ( T ) ( item.getMultiChoiceValue( internalName, safe ) as object );

                    if ( typeof( T ).Equals( typeof( SPLookupValue ) ) )
                        return ( T ) ( item.getValueAsSPLookupValue( internalName, safe ) as object );

                    if ( typeof( T ).Equals( typeof( Guid ) ) )
                        return ( T ) ( item.getValueAsGuid( internalName, safe ) as object );

                    return field == null ? default( T ) : ( T ) item[ field.Id ];
                }
                else
                {
                    if ( safe )
                        throw new InvalidFieldTypeException( internalName );
                    else
                        return default( T );
                }
            }
            else
            {
                if ( safe )
                    throw new SharePointListDoesNotContainSpecifiedFieldException( internalName );
                else
                    return default( T );
            }
        }

        static Guid getValueAsGuid( this SPListItem item, string internalName, bool safe )
        {
            if ( item.ContainsField( internalName ) )
            {
                SPField field = item.Fields.GetFieldByInternalName( internalName );
                SPFieldType t = field.Type;
                if ( t == SPFieldType.Lookup )
                    return ( Guid ) item[ field.Id ];
                else
                {
                    if ( safe )
                        throw new InvalidFieldTypeException( internalName, SPFieldType.Lookup );
                    else
                        return default( Guid );
                }
            }
            else
            {
                if ( safe )
                    throw new SharePointListDoesNotContainSpecifiedFieldException( internalName );
                else
                    return default( Guid );
            }
        }

        static SPFieldUrlValue getValueAsSPFieldUrlValue( this SPListItem item, string internalName, bool safe )
        {
            if ( item.ContainsField( internalName ) )
            {
                SPField field = item.Fields.GetFieldByInternalName( internalName );
                SPFieldType t = field.Type;
                if ( t == SPFieldType.URL &&
                    item[ field.Id ] != null &&
                    item[ field.Id ].ToString( ).Length != 0 )
                    return new SPFieldUrlValue( ( string ) item[ field.Id ] );
                else
                {
                    if ( safe )
                        throw new InvalidFieldTypeException( internalName, SPFieldType.URL );
                    else
                        return default( SPFieldUrlValue );
                }
            }
            else
            {
                if ( safe )
                    throw new SharePointListDoesNotContainSpecifiedFieldException( internalName );
                else
                    return default( SPFieldUrlValue );
            }
        }

        static SPLookupValue getValueAsSPLookupValue( this SPListItem item, string internalName, bool safe )
        {
            if ( item.ContainsField( internalName ) )
            {
                SPField field = item.Fields.GetFieldByInternalName( internalName );
                SPFieldType t = field.Type;
                if ( t == SPFieldType.Lookup )
                    return new SPLookupValue( ( string ) item[ field.Id ] );
                else
                {
                    if ( safe )
                        throw new InvalidFieldTypeException( internalName, SPFieldType.Lookup );
                    else
                        return default( SPLookupValue );
                }
            }
            else
            {
                if ( safe )
                    throw new SharePointListDoesNotContainSpecifiedFieldException( internalName );
                else
                    return default( SPLookupValue );
            }
        }

        static SPUserInfoExpanded getValueAsSPUserInfoExpanded( this SPListItem item, string internalName, bool safe )
        {
            if ( item.Fields.ContainsField( internalName ) )
            {
                SPFieldType t = item.Fields.GetFieldByInternalName( internalName ).Type;
                if ( t == SPFieldType.User )
                {
                    SPFieldLookupValue v = new SPFieldLookupValue( ( string ) item[ internalName ] );
                    return item.Web.GetUserByLoginName( v.LookupValue );
                }
                else
                {
                    if ( safe )
                        throw new InvalidFieldTypeException( internalName, SPFieldType.User );
                    else
                        return null;
                }
            }
            else
            {
                if ( safe )
                    throw new SharePointListDoesNotContainSpecifiedFieldException( internalName );
                else
                    return null;
            }
        }

        static void setValue<T>( this SPListItem item, string internalName, T value, bool safe )
        {
            if ( item.ContainsField( internalName ) )
            {
                if ( item.IsValidFieldType( internalName, typeof( T ) ) )
                {
                    SPField field = item.Fields.GetFieldByInternalName( internalName );
                    item[ field.Id ] = value;
                }
                else
                {
                    if ( safe )
                        throw new InvalidFieldTypeException( internalName );
                }
            }
            else
            {
                if ( safe )
                    throw new SharePointListDoesNotContainSpecifiedFieldException( internalName );
            }
        }

        #endregion Methods
    }
}